
#include <string.h>
#include <stdlib.h>
#include <sstream>
#include <iomanip>
#include <vector>

#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/filesystem.hpp>
using boost::system::error_code;
using boost::system::system_category;
using boost::system::system_error;

#include "filecell.hpp"


namespace	apcab	{

#define	IFSP_CACHE_SIZE		32

const std::string	FileCell::SLICE_BASENAME( "slice" );
const std::string	FileCell::DELETE_FILENAME( "DELETED" );

const u_int32_t     FileCell::SLICE_SIZE_THRESHOLD = 1024 * 1024 * 1024;


///////////////////////////////////////////////////////////////////////
//

FileCell::FileCell( const std::string data_dir )
		:	_data_dir( data_dir ),
			_ofs_append(),
			_ofs_delete(),
			_ifsp_cache( IFSP_CACHE_SIZE ),
			_buf_p(NULL),
			_buf_size( 0 )
{
	initialize();
}

FileCell::~FileCell()
{
	finalize();
}

///////////////////////////////////////////////////////////////////////
//

void
FileCell::initialize()
{
	_min_fileno = 0;
	_max_fileno = 0;

	scanDataDirectory();

}

void
FileCell::finalize()
{
	if (_ofs_append.is_open()) 
		_ofs_append.close();

	if (_ofs_delete.is_open())
		_ofs_delete.close();

	closeAllReadFiles();

	if (_buf_p) {
		free( _buf_p );

		_buf_p = NULL;
		_buf_size = 0;
	}
}

///////////////////////////////////////////////////////////////////////
//


int 
FileCell::put( const std::string& skey_b64,
		 const std::string& kpin_b64,
		 const std::string& key,
		 const std::string& data,
		 RecordLocation&    loc )
{
	std::ofstream&	ofs = openAppendFile();
	
	std::ostringstream	line;
	line << skey_b64 << "\t" << kpin_b64 << "\t" << key << "\t" << data << std::endl;

	loc.fileno = _max_fileno;
	loc.offset = ofs.tellp();
	loc.size = line.str().size();	 

	ofs << line.str();

	if (ofs.tellp() > SLICE_SIZE_THRESHOLD ) {
		ofs.close();
	}

	return	0;
}

int 
FileCell::get( const std::string& skey_b64,
		 const std::string& kpin_b64,
		 const std::string& key,
			   std::string& data,
		 const RecordLocation&  loc )
{ 
	std::ifstream&	ifs = openReadFile( loc.fileno );

	ifs.seekg( loc.offset, std::ios::beg );
	u_int32_t	where = ifs.tellg();
	if ( where != loc.offset ) {
		return -1;
	}

	/*
	char*	buf_p = getBuf( loc.size );
	*(buf_p + loc.size - 1) = 0;
	ifs.getline( buf_p, loc.size );
	u_int32_t	data_got_size = ifs.gcount();
	if ( (loc.size - data_got_size) > 1 ) {
		return -2;
	}

	char*	p_tab = buf_p;
	// skip skey_b64
	p_tab = strchr( p_tab + 1, '\t' );
	if ( NULL == p_tab ) {
		return -3;
	}
	// skip kpin_b64
	p_tab = strchr( p_tab + 1, '\t' );
	if ( NULL == p_tab ) {
		return -3;
	}
	// skip key
	p_tab = strchr( p_tab + 1, '\t' );
	if ( NULL == p_tab ) {
		return -3;
	}

	++p_tab;
	data.assign( p_tab, data_got_size - (p_tab - buf_p) );
	*/

	std::string	line;
	std::getline( ifs, line );

	std::vector< std::string >	fields;
	boost::split(fields, line, boost::is_any_of("\t"));
	if ( fields.size() != 4 ) {
		return	-4;
	}
	data = fields[ 3 ];

	return	0;
}

int 
FileCell::erase( const std::string&   skey_b64,
		   const std::string&   kpin_b64,
		   const std::string&   key,
		   const RecordLocation&    loc )
{
	std::ofstream&	ofs = openDeleteFile();

	ofs << skey_b64 << "\t" << kpin_b64 << "\t" << key << "\t"
		<< loc.fileno << "\t" << loc.offset << "\t" << loc.size << std::endl;

	return	0;
}


///////////////////////////////////////////////////////////////////////
//

std::ofstream&
FileCell::openAppendFile( void )
{
	if (_ofs_append.is_open()) {
		return _ofs_append;
	}

	// find the only APPEND file
	std::string	fn_append = getAppendFileName();
	_ofs_append.open( fn_append.c_str(), std::ios::app );
	if ( !_ofs_append ) {
		std::ostringstream	msg;
		msg << "fail to open append file " << fn_append;
		throw std::runtime_error( msg.str() );
	}

	return _ofs_append;
}

std::ofstream&
FileCell::openDeleteFile( void )
{
	if (_ofs_delete.is_open()) {
		return _ofs_delete;
	}

	// open the only DELETE file
	std::string	fn_delete = getDeleteFileName();
	_ofs_delete.open( fn_delete.c_str(), std::ios::app );
	if ( !_ofs_delete ) {
		std::ostringstream	msg;
		msg << "fail to open delete log file " << fn_delete;
		throw std::runtime_error( msg.str() );
	}

	return _ofs_delete;
}


std::string
FileCell::getAppendFileName( void )
{
	boost::filesystem::path	data_path( _data_dir );
	
	if (_max_fileno > 0) {
		std::string	fn = getCurrentAppendFileName();

		boost::filesystem::path	append_file = data_path / fn;
		if (boost::filesystem::exists( append_file ) ) {
			if (boost::filesystem::file_size(append_file) < SLICE_SIZE_THRESHOLD) {
				return append_file.string();
			}
		}
	}

	std::string	fn = getNextAppendFileName();
	boost::filesystem::path	append_file = data_path / fn;
	return append_file.string();
}


std::string
FileCell::getDeleteFileName( void )
{
	boost::filesystem::path	data_path( _data_dir );

	boost::filesystem::path	delete_path = data_path / DELETE_FILENAME;

	return delete_path.string();
}


std::string
FileCell::getNextAppendFileName( void )
{
	++_max_fileno;

	return getFileName( _max_fileno );
}

std::string
FileCell::getCurrentAppendFileName( void )
{
	return getFileName( _max_fileno );
}


std::string
FileCell::getFileName( u_int32_t fileno )
{
	std::ostringstream	fn;

	fn << SLICE_BASENAME << "." << std::setw(9) << std::setfill('0') << fileno;

	return fn.str();
}


///////////////////////////////////////////////////////////////////////
//

std::ifstream&  
FileCell::openReadFile( u_int32_t fileno )
{
	boost::mutex::scoped_lock	_scoped_lock( this->_ifsp_cache_mutex );

	ifsp_type	ifsp;

	if ( _ifsp_cache.exists( fileno ) ) {
		ifsp = _ifsp_cache.get( fileno );
		return *ifsp;
	}

	ifsp = new std::ifstream();
	std::string	fn = getReadFileName( fileno );
	ifsp->open( fn.c_str() );
	if ( !(ifsp->good()) ) {
		std::ostringstream	msg;
		msg << "fail to open file " << fn;
		throw std::runtime_error( msg.str() );
	}

	ifsp_type	ifsp_removed = _ifsp_cache.put( fileno, ifsp );
	if ( ifsp_removed ) {
		if (ifsp_removed->is_open()) {
			ifsp_removed->close();
		}

		delete ifsp_removed;
	}

	return *ifsp;
}


void
FileCell::closeAllReadFiles( void )
{
	boost::mutex::scoped_lock	_scoped_lock( this->_ifsp_cache_mutex );

	ifsp_cache_type::Key_List	_list( _ifsp_cache.get_all_keys() );
	for( ifsp_cache_type::Key_List_Iter liter = _list.begin(); liter != _list.end(); liter++ ) {
		ifsp_type	_ifsp = _ifsp_cache.get( *liter, false );

		if ( _ifsp->is_open() ) {
			_ifsp->close();
		}
		delete _ifsp ;
	}

	_ifsp_cache.clear();
}


std::string
FileCell::getReadFileName( u_int32_t fileno )
{
	boost::filesystem::path	data_path( _data_dir );
	
	std::string	fn = getFileName( fileno );
	boost::filesystem::path	file_path = data_path / fn;
	return file_path.string();
}


///////////////////////////////////////////////////////////////////////
//


char*
FileCell::getBuf( u_int32_t buf_size )
{
	if (buf_size <= _buf_size) {
		return	_buf_p;
	}

	char*	p = (char *)realloc(_buf_p, buf_size);
	if (NULL == p) {
		std::ostringstream	msg;
		msg << "fail to malloc buffer (size:" << buf_size << ")";
		throw std::runtime_error( msg.str() );
	}

	_buf_p = p;
	_buf_size = buf_size;

	return	_buf_p;
}


///////////////////////////////////////////////////////////////////////
//

void
FileCell::scanDataDirectory( void )
{
	boost::filesystem::path	data_path( _data_dir );

	if ( !boost::filesystem::exists( data_path ) ) {
		if ( !boost::filesystem::create_directory( data_path ) ) {
			throw boost::filesystem::filesystem_error( "fail to create directory", data_path, error_code(errno, system_category) );
		}
	}

	boost::filesystem::directory_iterator end_itr; // default construction yields past-the-end
	for ( boost::filesystem::directory_iterator itr( data_path );
		itr != end_itr;
		++itr ) {
		if (boost::filesystem::is_regular_file( itr->status() ) ) {
			std::string	bn = boost::filesystem::basename( itr->path() );
			if ( bn == SLICE_BASENAME ) {
				std::string	en = boost::filesystem::extension( itr->path() );
				u_int32_t	fn = 0;
				try {
					fn = boost::lexical_cast< u_int32_t >( en.c_str() + 1 );	// skip '.'
				}
				catch( std::exception& e ) {
					continue;
				}

				if ( (0 == _min_fileno) || (_min_fileno > fn) ) {
						_min_fileno = fn;
				}
				if ( (0 == _max_fileno) || (_max_fileno < fn) ) {
						_max_fileno = fn;
				}
			}
		}
	}

}


}	//// namespace apcab

